﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System.Collections.Generic;

using Nova.Parsing;

namespace Nova.CodeDOM
{
    /// <summary>
    /// Declares a class, which includes a name plus a body, along with various optional modifiers.
    /// </summary>
    /// <remarks>
    /// Non-nested classes can be only public or internal, and default to internal.
    /// Nested classes can be any of the 5 access types, and default to private.
    /// The accessibility of a contained type cannot exceed that of the parent type.
    /// Other valid modifiers include: new, abstract, sealed, partial
    /// Members of a class default to private.
    /// Allowed members are: ConstructorDecls, DestructorDecls, FieldDecls, MethodDecls, PropertyDecls,
    ///    IndexerDecls, OperatorDecls, EventDecls, DelegateDecls, ClassDecls, StructDecls, InterfaceDecls
    /// The optional base list can contain a single base class and/or one or more interfaces.
    /// Optional type parameters can be used for generic types, along with optional constraints.
    /// </remarks>
    public class ClassDecl : BaseListTypeDecl
    {
        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a <see cref="ClassDecl"/> with the specified name.
        /// </summary>
        public ClassDecl(string name, Modifiers modifiers)
            : base(name, modifiers)
        {
            // Check if we need to create a compiler-generated default constructor
            CheckGeneratedDefaultConstructor(true);
        }

        /// <summary>
        /// Create a <see cref="ClassDecl"/> with the specified name.
        /// </summary>
        public ClassDecl(string name)
            : this(name, Modifiers.None)
        { }

        /// <summary>
        /// Create a <see cref="ClassDecl"/> with the specified name, modifiers, and base types.
        /// </summary>
        public ClassDecl(string name, Modifiers modifiers, params Expression[] baseTypes)
            : base(name, modifiers, baseTypes)
        {
            // Check if we need to create a compiler-generated default constructor
            CheckGeneratedDefaultConstructor(true);
        }

        /// <summary>
        /// Create a <see cref="ClassDecl"/> with the specified name, modifiers, and type parameters.
        /// </summary>
        public ClassDecl(string name, Modifiers modifiers, params TypeParameter[] typeParameters)
            : base(name, modifiers, typeParameters)
        {
            // Check if we need to create a compiler-generated default constructor
            CheckGeneratedDefaultConstructor(true);
        }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// Always <c>true</c>.
        /// </summary>
        public override bool IsClass
        {
            get { return true; }
        }

        /// <summary>
        /// The keyword associated with the <see cref="Statement"/>.
        /// </summary>
        public override string Keyword
        {
            get { return ParseToken; }
        }

        #endregion

        #region /* METHODS */

        /// <summary>
        /// Add a <see cref="CodeObject"/> to the <see cref="ClassDecl"/> body.
        /// </summary>
        public override void Add(CodeObject obj)
        {
            // Check if we need to remove a compiler-generated default constructor
            if (obj is ConstructorDecl)
                CheckRemoveGeneratedDefaultConstructor((ConstructorDecl)obj);
            base.Add(obj);
        }

        /// <summary>
        /// Add multiple <see cref="CodeObject"/>s to the <see cref="ClassDecl"/> body.
        /// </summary>
        public override void Add(params CodeObject[] objects)
        {
            foreach (CodeObject obj in objects)
                Add(obj);
        }

        /// <summary>
        /// Add a collection of <see cref="CodeObject"/>s to the <see cref="ClassDecl"/> body.
        /// </summary>
        /// <param name="collection">The collection to be added.</param>
        public override void AddRange(IEnumerable<CodeObject> collection)
        {
            foreach (CodeObject codeObject in collection)
                Add(codeObject);
        }

        /// <summary>
        /// Insert a <see cref="CodeObject"/> at the specified index in the <see cref="ClassDecl"/> body.
        /// </summary>
        /// <param name="index">The index at which to insert.</param>
        /// <param name="obj">The CodeObject to be inserted.</param>
        public override void Insert(int index, CodeObject obj)
        {
            // Check if we need to remove a compiler-generated default constructor
            if (obj is ConstructorDecl)
                CheckRemoveGeneratedDefaultConstructor((ConstructorDecl)obj);
            base.Insert(index, obj);
        }

        /// <summary>
        /// Remove the specified <see cref="CodeObject"/> from the <see cref="ClassDecl"/> body.
        /// </summary>
        public override void Remove(CodeObject obj)
        {
            base.Remove(obj);
            // Check if we need to create a compiler-generated default constructor
            if (obj is ConstructorDecl)
                CheckGeneratedDefaultConstructor(true);
        }

        /// <summary>
        /// Remove all <see cref="CodeObject"/>s from the <see cref="ClassDecl"/> body.
        /// </summary>
        public override void RemoveAll()
        {
            base.RemoveAll();
            // Check if we need to create a compiler-generated default constructor
            CheckGeneratedDefaultConstructor(true);
        }

        /// <summary>
        /// Get the base type.
        /// </summary>
        public override TypeRef GetBaseType()
        {
            List<Expression> baseTypes = GetAllBaseTypes();
            if (baseTypes != null)
            {
                foreach (Expression baseTypeExpression in baseTypes)
                {
                    TypeRef baseTypeRef = baseTypeExpression.SkipPrefixes() as TypeRef;
                    if (baseTypeRef != null && baseTypeRef.IsClass)
                        return baseTypeRef;
                }
            }
            return TypeRef.ObjectRef;
        }

        /// <summary>
        /// Determine if the class is a subclass of the specified class.
        /// </summary>
        public override bool IsSubclassOf(TypeRef classTypeRef)
        {
            TypeRef baseTypeRef = GetBaseType();
            while (baseTypeRef != null)
            {
                if (baseTypeRef.IsSameRef(classTypeRef))
                    return true;
                if (baseTypeRef.IsSameRef(TypeRef.ObjectRef))
                    return false;
                baseTypeRef = baseTypeRef.GetBaseType() as TypeRef;
            }
            return false;
        }

        /// <summary>
        /// Check if we need to create or remove a compiler-generated default constructor.
        /// </summary>
        public void CheckGeneratedDefaultConstructor(bool currentPartOnly)
        {
            NamedCodeObjectGroup constructors = GetConstructors();

            // Add or remove compiler-generated default constructors as necessary
            if (constructors == null || constructors.Count == 0)
            {
                // Add a compiler-generated default public constructor if we don't have any constructors yet,
                // and this isn't a static class.
                if (!IsStatic)
                    base.Add(new ConstructorDecl(Modifiers.Public) { IsGenerated = true, IsSingleLine = true });
            }
            else if (constructors.Count > 1)
            {
                // Remove any duplicate compiler-generated default constructors (can occur for partial types during multithreaded
                // parsing), and if we have any other non-static constructors, then remove all default constructors.
                bool removeAllDefaults = false;
                ConstructorDecl defaultConstructor = null;
                foreach (ConstructorDecl constructor in constructors)
                {
                    ConstructorDecl removeConstructor = null;
                    if (constructor.IsGenerated && constructor.ParameterCount == 0)
                    {
                        if (removeAllDefaults)
                            removeConstructor = constructor;
                        else if (defaultConstructor != null)
                        {
                            if (constructor.Parent == this)
                                removeConstructor = constructor;
                            else
                            {
                                removeConstructor = defaultConstructor;
                                defaultConstructor = constructor;
                            }
                        }
                        else
                            defaultConstructor = constructor;
                    }
                    else if (!constructor.IsStatic)
                    {
                        removeAllDefaults = true;
                        if (defaultConstructor != null)
                            removeConstructor = defaultConstructor;
                    }
                    if (removeConstructor != null)
                    {
                        // Don't remove if we're doing the current part only and it belongs to another part
                        if (!currentPartOnly || removeConstructor.Parent == this)
                        {
                            // Remove via its parent in case it belongs to another part of a partial type
                            ((BlockStatement)removeConstructor.Parent).Body.Remove(removeConstructor);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Check if we need to remove a compiler-generated default constructor when adding a constructor.
        /// </summary>
        protected void CheckRemoveGeneratedDefaultConstructor(ConstructorDecl constructorDecl)
        {
            // If we're adding a non-static constructor, we need to remove any generated ones
            if (!constructorDecl.IsStatic)
            {
                NamedCodeObjectGroup constructors = GetConstructors();
                if (constructors != null && constructors.Count > 0)
                {
                    foreach (ConstructorDecl constructor in constructors)
                    {
                        if (constructor.IsGenerated && constructor.ParameterCount == 0)
                        {
                            // Remove via its parent in case it belongs to another part of a partial type
                            ((BlockStatement)constructor.Parent).Body.Remove(constructor);
                        }
                    }
                }
           }
        }

        #endregion

        #region /* PARSING */

        /// <summary>
        /// The token used to parse the code object.
        /// </summary>
        public new const string ParseToken = "class";

        internal static void AddParsePoints()
        {
            // Classes are only valid with a Namespace or TypeDecl parent, but we'll allow any IBlock so that we can
            // properly parse them if they accidentally end up at the wrong level (only to flag them as errors).
            // This also allows for them to be embedded in a DocCode object.
            Parser.AddParsePoint(ParseToken, Parse, typeof(IBlock));
        }

        /// <summary>
        /// Parse a <see cref="ClassDecl"/>.
        /// </summary>
        public static ClassDecl Parse(Parser parser, CodeObject parent, ParseFlags flags)
        {
            return new ClassDecl(parser, parent);
        }

        protected ClassDecl(Parser parser, CodeObject parent)
            : base(parser, parent)
        {
            MoveComments(parser.LastToken);        // Get any comments before 'class'
            parser.NextToken();                    // Move past 'class'
            ParseNameTypeParameters(parser);       // Parse the name and any type parameters
            ParseModifiersAndAnnotations(parser);  // Parse any attributes and/or modifiers

            // Move any trailing compiler directives to the Infix1 position (assume we have a base-type list)
            MoveAnnotations(AnnotationFlags.IsPostfix, AnnotationFlags.IsInfix1);

            ParseBaseTypeList(parser);        // Parse the optional base-type list
            ParseConstraintClauses(parser);   // Parse any constraint clauses

            // Move any trailing post annotations on the last base type to the first constraint (if any)
            AdjustBaseTypePostComments();

            // If we don't have a base-type list, move any trailing compiler directives to the Postfix position
            if (_baseTypes == null || _baseTypes.Count == 0)
                MoveAnnotations(AnnotationFlags.IsInfix1, AnnotationFlags.IsPostfix);

            new Block(out _body, parser, this, true);  // Parse the body

            // Eat any trailing terminator (they are allowed but not required on non-delegate type declarations)
            if (parser.TokenText == ParseTokenTerminator)
                parser.NextToken();

            // Check if we need to create or remove a compiler-generated default constructor.  In this case, we
            // might end up removing one from a different part than this one - this is necessary for single-
            // threaded parsing, and it's also thread-safe because both this class and any other parts won't
            // be visible by other threads until after the parsing of their parent is complete.
            CheckGeneratedDefaultConstructor(false);
        }

        #endregion

        #region /* FORMATTING */

        /// <summary>
        /// Determine a default of 1 or 2 newlines when adding items to a <see cref="Block"/>.
        /// </summary>
        public override int DefaultNewLines(CodeObject previous)
        {
            // Always default to a blank line before a class declaration
            return 2;
        }

        #endregion
    }
}
